// HTTP model implementation
// Copyright 2006 (C) Ralph Thomas

#include <model/http/httpModel.h>
#include <model/http/httpWorkerPool.h>

namespace model {
	httpModel::modelMap	httpModel::_modelMap;
	httpModel::mutex	httpModel::_modelMutex;
	httpModel::httpModel( const std::string& theUrl )
	: _refCount( 1 ), _theUrl( theUrl ), _error( 0 ), _loadQueued( false ) {}
	httpModel::~httpModel() {}
	std::string httpModel::_getParentUrl() const {
		lock lk( _mutex );
		url* myUrl( url::fromString( _theUrl ) );
		if( !myUrl ) return "";
		//
		// We have no idea what the "parent" of any
		// URL is; that concept has no meaining for
		// HTTP. Just return the main index.
		//
		std::string parent( myUrl->getUrlWithoutParts(
			url::kPath | url::kQuery ) );
		delete myUrl;
		return parent;
	}
	void httpModel::load( const std::string& type, const std::string& data ) {
		lock lk( _mutex );
		_contents = data; _type = type; _loadQueued = false;
		_receivers.notify( this, kData );
		_receivers.notify( this, kType );
	}
	model* httpModel::fromString( const std::string& serialized ) {
		//
		// We do the following things here:
		//  1. Verify that the protocol HTTP or HTTPS.
		//  2. Assume that the rest of the URL is okay; return a model.
		//
		if( serialized.find( "http://" ) != 0 && serialized.find( "https://" ) != 0 ) return 0;
		//
		// We're going to use the modelMap; lock it first.
		//
		lock lk( _modelMutex );
		//
		// See if we've already created this model.
		//
		modelMap::iterator i( _modelMap.find( serialized ) );
		if( i == _modelMap.end() ) {
			//
			// This model doesn't exist yet; create and return
			// it.
			//
			httpModel* hm( new httpModel( serialized ) );
			//
			// Store this model so that we can return it from this
			// method in the future.
			//
			_modelMap[serialized] = hm;
			return hm;
		}
		//
		// This model already exists; increase refcount and return it.
		//
		return i->second->retain();
	}
	model* httpModel::retain() const {
		lock lk( _mutex );
		httpModel* us( const_cast<httpModel*>( this ) );
		us->_refCount++;
		return us;
	}
	void httpModel::release() {
		lock lk( _mutex );
		_refCount--;
		if( !_refCount ) {
			//
			// We can release ourselves.
			//
			lock lk2( _modelMutex );
			modelMap::iterator i = _modelMap.find( _theUrl );
			if( i != _modelMap.end() ) _modelMap.erase( i );
			lk.unlock(); delete this;
		}
	}
	void httpModel::subscribe( receiver* r ) {
		lock lk( _mutex );
		_receivers.subscribe( r );
	}
	void httpModel::unsubscribe( const receiver* r ) {
		lock lk( _mutex );
		_receivers.unsubscribe( r );
	}
	adobe::value_t httpModel::getAttribute( const std::string& attrName ) const {
		lock lk( _mutex );
		if( attrName == kUrl ) {
			return adobe::value_t( _theUrl );
		} else if( attrName == kModelValid ) {
			if( _error == 0 ) return adobe::value_t( true );
			return adobe::value_t( false );
		} else if( attrName == kModelAttributes ) {
			attributeInfo ai;
			//
			// This whole model is read-only; there is no PUT
			// support yet.
			//
			ai.addAttribute( kModelValid, false );
			ai.addAttribute( kModelAttributes, false );
			ai.addAttribute( kName, false );
			ai.addAttribute( kType, false );
			ai.addAttribute( kData, false );
			ai.addAttribute( kParent, false );
			ai.addAttribute( kError, false );
			ai.addAttribute( kSize, false );
			return adobe::value_t( ai );
		} else if( attrName == kName ) {
			//
			// We could do something like extracting the <title>
			// from HTML here, or just taking the basename of the
			// URL part, but either way it's not terribly useful.
			//
			return adobe::value_t( _theUrl );
		} else if( attrName == kType ) {
			std::string deftype( "x-mission/x-file" );
			//
			// If we don't have a type, and haven't queued one
			// up to fetch then do it here.
			// 
			if( !_type.empty() ) return adobe::value_t( _type );
			if( _loadQueued )
				return adobe::value_t( deftype );
			httpFetch( this, _theUrl );
			_loadQueued = true;
			return adobe::value_t( deftype );
		} else if( attrName == kData ) {
			//
			// Similar to the above code for type.
			//
			if( !_contents.empty() )
				return adobe::value_t( _contents );
			if( _loadQueued )
				return adobe::value_t( _contents );
			httpFetch( this, _theUrl );
			_loadQueued = true;
			return adobe::value_t( _contents );
		} else if( attrName == kParent ) {
			return adobe::value_t( _getParentUrl() );
		} else if( attrName == kError ) {
			return adobe::value_t( _error );
		} else if( attrName == kSize ) {
			return adobe::value_t( _contents.size() );
		}
		return adobe::value_t();
	}
	void httpModel::setAttribute( const std::string& attrName,
		const adobe::value_t& val )
	{
		lock lk( _mutex );
		//
		// This model is read only. Only error values can be set.
		//
		if( attrName == kError ) {
			if( !val.get( _error ) ) return;
			if( _error == 0 ) return;
			//
			// Notify all of our subscribers that we are in
			// an error state.
			//
			_receivers.notify( this, kError );
			_receivers.notify( this, kModelValid );
		}
	}
	adobe::value_t httpModel::execute( const std::string& action,
		const adobe::value_t& arg ) {
		lock lk( _mutex );
		//
		// The only action we support is "reload".
		// 
		if( action == "reload" ) {
			if( _loadQueued ) return adobe::value_t();
			_loadQueued = true;
			httpFetch( this, _theUrl );
		}
		return adobe::value_t();
	}
};

